Previous topicNext topic
Help > Objects and COM Programming >
What are Connection Points?

Generally speaking, a client module calls a server module to perform specific operations as they are needed. However, in many situations, it's convenient and efficient for a server to notify its client of a condition or event immediately, without forcing the client to inquire about the status. At the appropriate time, the server calls back to a client method, passing information via the method parameters. This is the exact opposite of normal communication, because the server module is now calling the client module. In effect, the client is acting as a server for the purpose of handling these events. In the world of objects, a server which can call such "Event Methods" is said to offer a "Connection Point". A Connection Point can be used with COM objects or internal objects. Further, it may use either a direct interface or the DISPATCH interface. Event methods may take parameters, but may not return a result.

In COM terminology, a server which offers a Connection Point is known as an "Event Source". A client which can attach to a Connection Point and handle events is known as an "Event Sink" or "Event Handler". The terms source and sink are analogous to the electrical engineering terms source and sink.

Perhaps you have a server object which performs complex arithmetic, and may take quite some time to finish. You'd like to notify the client of your progress towards completion at regular intervals. In that way, the client can continue other work, or just notify the user of the status. If a server object offers a Connection Point, it must declare the event interface:

INTERFACE STATUS $StatusGuid AS EVENT

 INHERIT IUNKNOWN

 METHOD Progress(Percent AS LONG)

END INTERFACE

Finally, the server class must include a declaration of the event interfaces it supports via a Connection Point by adding one or more EVENT SOURCE statements within the class definition:

EVENT SOURCE STATUS

EVENT SOURCE DISPATCH

Each server class created by PowerBASIC may offer up to four event interfaces. A client module may subscribe to any or all of these event interfaces. When it's time for the server object to notify the client of an event, the RAISEEVENT statement is used. For the Dispatch interface, OBJECT RAISEEVENT is used instead. RAISEEVENT may only appear within a class which declares the Event Source interface. The concept of RAISEEVENT is very similar to the CALL statement, but it may only be used to execute event methods:

RaiseEvent Status.Progress(10) ' advise the code is 10% done

It should be noted that RaiseEvent does not reference an object variable at all, because it calls any and all Event Methods which are currently attached to the Connection Point. Instead, it references the interface name (in this case "Status"), followed by the name of the Event Method to be executed (in this case "Progress").

The client may choose to support the event by creating the appropriate event code (it must precisely match the declaration in the server), or the client could just ignore the event completely. If supported, the client must have an event method to handle the event, and create an event object to do so. In effect, the client actually becomes an object server for this one purpose. The client code might be something like:

CLASS EventClass AS EVENT

 INTERFACE STATUS AS EVENT

   INHERIT IUNKNOWN

   METHOD Progress(Percent AS LONG)

     CALL DisplayIt(Percent)

   END METHOD

 END INTERFACE

END CLASS

In addition, the client must initiate a connection to the server with EVENTS FROM, and disconnect when done with EVENTS END:

DIM oEvent AS STATUS

oEvent = CLASS "EventClass"

EVENTS FROM MyObject CALL oEvent

 

' execute some code here...

 

EVENTS END oEvent

A Connection Point may be attached to one Event Method, multiple Event Methods, or no Event Method at all. Whenever a RAISEEVENT statement is executed, all Event Methods attached to the source object are called, one after another. There is no guarantee of the sequence of the calls, and you must consider the possibility that RAISEEVENT with a ByRef parameter could change the value of a parameter variable before any particular Event Method is executed.

Here is a complete program which demonstrates the execution of a Connection Point in a single, self-contained application. It uses only internal objects. Since the objects are all internal, it is not necessary to assign a GUID to each class and interface.

#COMPILE EXE

 

CLASS EvClass AS EVENT

 INTERFACE Status AS EVENT

   INHERIT IUNKNOWN

   METHOD Done

     MSGBOX "Done!"

   END METHOD

 END INTERFACE

END CLASS

 

CLASS MyClass

 INTERFACE MyMath

   INHERIT IUNKNOWN

   METHOD DoMath

     MSGBOX "Calculating..."   ' Do some math calculations here

     RAISEEVENT Status.Done()

   END METHOD

 END INTERFACE

 

 EVENT SOURCE Status

 

END CLASS

 

FUNCTION PBMAIN()

 DIM oMath AS MyMath, oStatus AS Status

 LET oMath = CLASS "MyClass"

 LET oStatus = CLASS "EvClass"

 

 EVENTS FROM oMath CALL oStatus

 oMath.DoMath

 EVENTS END oStatus

END FUNCTION

Here is a set of programs which demonstrate the execution of a Connection Point using a COM SERVER and a COM CLIENT. It uses an in-process COM server (DLL created with PB/Win), and a COM CLIENT as an executable program. First the COM SERVER:

#COMPILE DLL "EvServer.dll"

 

$EvIFaceGuid = GUID$("{00000098-0000-0000-0000-000000000002}")

$MyClassGuid = GUID$("{00000098-0000-0000-0000-000000000003}")

$MyIFaceGuid = GUID$("{00000098-0000-0000-0000-000000000004}")

 

INTERFACE Status $EvIFaceGuid AS EVENT

 INHERIT IUNKNOWN

 METHOD Done

END INTERFACE

 

CLASS MyClass $MyClassGuid AS COM

 INTERFACE MyMath $MyIFaceGuid

   INHERIT IUNKNOWN

   METHOD DoMath

     MSGBOX "Calculating..."   ' Do some math calculations here

     RAISEEVENT Status.Done()

   END METHOD

 END INTERFACE

 

 EVENT SOURCE Status

 

END CLASS

Next the COM CLIENT:

#COMPILE EXE "EvClient.exe"

 

$EvClassGuid = GUID$("{00000098-0000-0000-0000-000000000001}")

$EvIFaceGuid = GUID$("{00000098-0000-0000-0000-000000000002}")

$MyIFaceGuid = GUID$("{00000098-0000-0000-0000-000000000004}")

 

CLASS EvClass $EvClassGuid AS EVENT

 INTERFACE STATUS $EvIFaceGuid AS EVENT

   INHERIT IUNKNOWN

   METHOD Done

     MSGBOX "Done!"

   END METHOD

 END INTERFACE

END CLASS

 

INTERFACE MyMath $MyIFaceGuid

 INHERIT IUNKNOWN

 METHOD DoMath

END INTERFACE

 

FUNCTION PBMAIN()

 DIM oMath AS MyMath

 DIM oStatus AS STATUS

 

 LET oMath = NEWCOM "MyClass"

 LET oStatus = CLASS "EvClass"

 

 EVENTS FROM oMath CALL oStatus

 oMath.DoMath

 EVENTS END oStatus

END FUNCTION

 

See Also

What is an object, anyway?

Just what is COM?

Enumerating Collections

What are Type Libraries?